package controlSoftware.UserThread;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import controlSoftware.DataIn.DataIn;
import controlSoftware.DataProcess.Coordinate;
import controlSoftware.DataProcess.DataProcess;
import controlSoftware.DataProcess.Device;
import controlSoftware.DataProcess.SensorData;
import controlSoftware.DataProcess.SysModel;
import controlSoftware.DataProcess.Velocity;
import controlSoftware.GUI.User;
import controlSoftware.GUI.mainUI;
import controlSoftware.util.Common;
import controlSoftware.util.RecordHandler;

public class UserThread {
	private int id;
	private String ip;
	private int port;
	private RecordHandler record;
	private DataIn dataIn;
	private DataProcess dataProcess;
	private Queue<SensorData> userData;
	private Device deviceObj;
	private RecordHandler recordmgr;

	public static String[] recordFilePaths;

	public static Thread mapRenderThread;

	/**
	 * user thread constructed with telnet connection
	 * 
	 * @param id
	 * @param ip
	 * @param port
	 * @param record
	 */
	public UserThread(int id, String ip, int port, Device device,
			RecordHandler record) {
		this.id = id;
		this.ip = ip;
		this.port = port;
		this.record = record;
		this.deviceObj = device;
		this.recordmgr = record;

		// create a new queue to store incoming imu measurements
		this.userData = new ConcurrentLinkedQueue<SensorData>();

		// generate new threads for reading and processing data
		this.dataIn = new DataIn(id, "User" + id + "DataIn", userData, ip,
				port, record);
		this.dataProcess = new DataProcess(id, "User" + id + "DataProcessing",
				userData, deviceObj);
	}

	/**
	 * user thread constructed with mock data input
	 * 
	 * @param id
	 * @param filePath
	 */
	public UserThread(int id, String filePath, Device device) {

		// create a new device object, with default user object
		deviceObj = device;

		Queue<SensorData> userData = new ConcurrentLinkedQueue<SensorData>();

		SysModel.isInitState = true;

		dataIn = new DataIn(id, "User" + id + "DataIn", userData, filePath);
		dataProcess = new DataProcess(id, "User" + id + "DataProcessing",
				userData, deviceObj);

		mainUI.usingMockDataFlag = true;
	}

	final private static String deviceListSchemaFilepath = "xml/deviceListSchema.xsd";

	/**
	 * For real time tracking Parses the deviceList xml to get device attributes
	 * and init a thread for each If recording the session, create a data file
	 * for each device and a session xml
	 */
	public static void initThread() {

		try {

			Document doc_record = null;
			Element rootElement = null;

			// parses deviceList.xml to obtain all the devices
			File fXmlFile = new File("xml/deviceList.xml");
			SchemaFactory factory = SchemaFactory
					.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = factory
					.newSchema(new File(deviceListSchemaFilepath));
			Validator validator = schema.newValidator();

			DocumentBuilderFactory builderFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder dBuilder = builderFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);

			validator.validate(new DOMSource(doc));
			doc.getDocumentElement().normalize();

			// create a new record xml
			if (mainUI.isRecording) {
				doc_record = dBuilder.newDocument();

				// root element
				rootElement = doc_record.createElement("recordSession");
				doc_record.appendChild(rootElement);

				// set attribute to root element
				Attr attr = doc_record.createAttribute("xmlns:xsi");
				attr.setValue("http://www.w3.org/2001/XMLSchema-instance");
				rootElement.setAttributeNode(attr);
			}

			// retrieve all the active devices
			NodeList nActiveDevices = doc.getElementsByTagName("device");

			if (mainUI.isRecording) {
				recordFilePaths = new String[nActiveDevices.getLength()];
			}

			for (int i = 0; i < nActiveDevices.getLength(); i++) {
				Node nDevice = nActiveDevices.item(i);

				if (nDevice.getNodeType() == Node.ELEMENT_NODE) {

					// Initialize the user object within the user thread device
					User userObj = new User();

					// fill in user attributes
					NodeList users = (((Element) nDevice)
							.getElementsByTagName("user"));
					Node user = users.item(0);
					userObj.setsFirstName(Common.getTextValue((Element) user,
							"firstName"));
					userObj.setsLastName(Common.getTextValue((Element) user,
							"lastName"));
					userObj.setsIcon(Common
							.getTextValue((Element) user, "icon"));
					userObj.setsMappedDevice(Integer
							.valueOf(((Element) nDevice).getAttribute("id")));

					System.out.println(userObj.getsFirstName());

					Device deviceObj = new Device(
							Integer.valueOf(((Element) nDevice)
									.getAttribute("id")), userObj);

					RecordHandler record = null;
					// copies the information to record xml
					if (mainUI.isRecording) {
						// create device element
						Element device = doc_record.createElement("device");

						// set attribute to device element
						Attr id = doc_record.createAttribute("id");
						id.setValue(((Element) nDevice).getAttribute("id"));
						device.setAttributeNode(id);

						// create and set user element
						Element eUser = doc_record.createElement("user");
						Element first = doc_record.createElement("firstName");
						Element last = doc_record.createElement("lastName");
						Element icon = doc_record.createElement("icon");

						first.appendChild(doc_record.createTextNode(Common
								.getTextValue((Element) user, "firstName")));
						last.appendChild(doc_record.createTextNode(Common
								.getTextValue((Element) user, "lastName")));
						icon.appendChild(doc_record.createTextNode(Common
								.getTextValue((Element) user, "icon")));

						eUser.appendChild(first);
						eUser.appendChild(last);
						eUser.appendChild(icon);
						device.appendChild(eUser);

						Element filePath = doc_record
								.createElement("recordFilePath");
						DateFormat timeFormat = new SimpleDateFormat(
								"yyyy-MM-dd_hh-mm-ss");
						String timeStamp = timeFormat.format(new Date());
						String fileName = "record_" + timeStamp + "_device-"
								+ ((Element) nDevice).getAttribute("id")
								+ ".txt";

						filePath.appendChild(doc_record
								.createTextNode("mockData/" + fileName));
						device.appendChild(filePath);

						rootElement.appendChild(device);

						record = new RecordHandler("mockData/" + fileName);
					}

					// creates a new user thread object
					UserThread user_thread = new UserThread(
							Integer.valueOf(((Element) nDevice)
									.getAttribute("id")),
							Common.getTextValue((Element) nDevice, "ip"),
							Common.getIntValue((Element) nDevice, "portNumber"),
							deviceObj, record);

					// add the thread to userPool
					mainUI.userPool.add(user_thread);
					mainUI.listModel.addElement(deviceObj);
				}

				if (mainUI.isRecording) {
					// write the content into xml file
					TransformerFactory transformerFactory = TransformerFactory
							.newInstance();
					Transformer transformer = transformerFactory
							.newTransformer();
					DOMSource source = new DOMSource(doc_record);
					DateFormat timeFormat = new SimpleDateFormat(
							"yyyy-MM-dd_hh-mm-ss");
					String timeStamp = timeFormat.format(new Date());
					StreamResult result = new StreamResult(new File("records/"
							+ timeStamp + ".xml"));
					transformer.transform(source, result);

					System.out.println("File saved!");
				}

			}

		} catch (Exception e) {
			System.out
					.println("Error encountered during deviceList.xml parsing"
							+ e.getMessage());
		}

		if (mainUI.userPool.size() > 0) {
			mainUI.userPool.start();
			mapRenderThread = new Thread(mainUI.mapRenderer);
			mapRenderThread.start();
		}
	}

	/**
	 * initThread for record playback Parses the specified xml for device
	 * attributes and start a thread for each
	 * 
	 * @param recordFilePath
	 */
	public static void initThread(String recordFilePath) {
		try {
			// parses deviceList.xml to obtain all the devices
			File fXmlFile = new File(recordFilePath);
			// SchemaFactory factory =
			// SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			// Schema schema = factory.newSchema(new
			// File(deviceListSchemaFilepath));
			// Validator validator = schema.newValidator();
			//
			DocumentBuilderFactory builderFactory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder dBuilder = builderFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);

			// validator.validate(new DOMSource(doc));
			doc.getDocumentElement().normalize();

			// retrieve all the active devices
			NodeList nActiveDevices = doc.getElementsByTagName("device");

			for (int i = 0; i < nActiveDevices.getLength(); i++) {

				Node nDevice = nActiveDevices.item(i);

				if (nDevice.getNodeType() == Node.ELEMENT_NODE) {

					// Initialize the user object within the user thread device
					User userObj = new User();

					// fill in user attributes
					NodeList users = (((Element) nDevice)
							.getElementsByTagName("user"));
					Node user = users.item(0);
					userObj.setsFirstName(Common.getTextValue((Element) user,
							"firstName"));
					userObj.setsLastName(Common.getTextValue((Element) user,
							"lastName"));
					userObj.setsIcon(Common
							.getTextValue((Element) user, "icon"));
					userObj.setsMappedDevice(Integer
							.valueOf(((Element) nDevice).getAttribute("id")));

					System.out.println(userObj.getsFirstName());

					Device deviceObj = new Device(
							Integer.valueOf(((Element) nDevice)
									.getAttribute("id")), userObj);

					// creates a new user thread object
					UserThread user_thread = new UserThread(
							Integer.valueOf(((Element) nDevice)
									.getAttribute("id")), Common.getTextValue(
									(Element) nDevice, "recordFilePath"),
							deviceObj);

					// add the thread to userPool
					mainUI.userPool.add(user_thread);
					mainUI.listModel.addElement(deviceObj);
				}
			}

		} catch (Exception e) {
			System.out.println("Error encountered during record xml parsing"
					+ e.getMessage());
		}

		if (mainUI.userPool.size() > 0) {
			mainUI.userPool.start();
			
			if(mapRenderThread.isAlive() != true){
				mapRenderThread = new Thread(mainUI.mapRenderer);
				mapRenderThread.start();
			}
		}

	}

	/**
	 * start a user thread with data retrieval and processing
	 */
	public void start() {
		mainUI.isPaused = false;

		if (dataIn.isAlive() != true ) {
			dataIn.start();
		}
		
		if( dataProcess.isAlive() != true){
			dataProcess.start();
		}
		
		System.out.println("UserThread " + id + " has been started.");
		mainUI.statusBar.setText("UserThread " + id + " has been started.");

	}

	public void stop() {	
		synchronized(this){
		mainUI.isPaused = true;
		
		//reset coordinates
		userData.clear();
		
		SensorData currentData = new SensorData(0, 0, 0, 0, 0, 0, 0);
		Coordinate prevCoordinate = new Coordinate(120, 70);
		Velocity prevVelocity = new Velocity(0, 0);
		dataProcess.setPrevData(currentData);
		dataProcess.setPrevCoordinate(prevCoordinate);
		dataProcess.setPrevVelocity(prevVelocity);
		userData.add(currentData);
		//dataProcess.viewControl.updateView(currentData, prevCoordinate);
		
		dataProcess.viewControl.deviceLabel.setBounds(120,70,30,30);
		
		//clear device list	
		mainUI.listModel.clear();

		System.out.println("UserThread " + id + " has been stopped.");
		mainUI.statusBar.setText("UserThread " + id + " has been stopped.");
		}
	}

	public void restart() {
		mainUI.isPaused = false;

	}

	public Device getDeviceProperties() {
		return deviceObj;
	}

	public DataIn getDataInThread() {
		return dataIn;
	}

	public DataProcess getDataProcessThread() {
		return dataProcess;
	}

	public Queue<SensorData> getUserDataQueue() {
		return userData;
	}

}